package img.window;

import com.formdev.flatlaf.FlatLightLaf;
import img.HeicToJpegUtil;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.io.File;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.prefs.Preferences;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static img.window.MainWindow.*;
/**
 * A small GUI program based on 'NeverMendel's heif-convert' command line program, that converts '.heic' image files to another format of your choice (i.e. jpg, png, etc.).
 * 一个基于“NeverMendel的heif-convert”命令行程序的小型GUI程序，可以将“.heic”图像文件转换为您选择的另一种格式（如jpg、png等）。
 * https://github.com/everdro1d/heic-photo-converter
 * https://github.com/NeverMendel/heif-convert
 */
public class MainWorker {
    public final static String versionTag = "v1.0.0";
    private static String jarPath;
    private static final String fileDiv = File.separator;
    // public static boolean debug = false;
    public static boolean debug = true;
    protected static String format = "jpg";
    protected static String selectedFilePath = "";
    private static String heifConvertPath; // heif-convert.exe程序路径
    protected static boolean deleteOriginal = false; // 删除原始文件
    /**
     * JDK中的Preferences类也提供了一个让Java应用程序存放配置信息的统一方法，即每一个基于Java的应用程序都可以使用Preferences类存放自己的配置信息。
     * Preferences类将应用程序的配置信息存放在具体的操作系统平台上，具体来说，在Windows操作系统下存放在注册表中，在Linux平台下是放在使用应用程序的用户的home目录下面的一个隐藏文件中。
     */
    public static final Preferences prefs = Preferences.userNodeForPackage(MainWorker.class);

    public static void main(String[] args) {
        // 检查操作系统兼容性
        checkOSCompatability();
        // 获取当前JAR文件的路径
        getJarPath();

        // heifConvertPath = jarPath + fileDiv + "heif-convert.exe";
        // copyBinaryTempFile();

        FlatLightLaf.setup();

        setUI();

        prefs();

        EventQueue.invokeLater(() -> {
            try {
                new MainWindow();
            } catch (Exception e) {
                e.printStackTrace(System.err);
                System.err.println("Failed to start main window.");
            }
        });
    }

    private static void setUI() {
        try {
            // 仿IntelliJ Darcula的Swing主题FlatLaf使用方法 https://www.cnblogs.com/sandeepin/p/flatlaf-swing-ui-theme.html
            UIManager.setLookAndFeel(new FlatLightLaf());
            UIManager.put("RootPane.background", new Color(0xe1e1e1));
            UIManager.put("RootPane.foreground", new Color(0x000000));

            // 圆角方角
            UIManager.put("Component.arc", 10);
            UIManager.put("TextComponent.arc", 10);
            UIManager.put("Separator.stripeWidth", 10);
            UIManager.put("RootPane.background", new Color(0xe1e1e1));
            UIManager.put("RootPane.foreground", new Color(0x000000));

            UIManager.put("OptionPane.minimumSize", new Dimension(300, 100));
            UIManager.put("OptionPane.messageFont", new Font(fontName, Font.PLAIN, 14));
            UIManager.put("OptionPane.buttonFont", new Font(fontName, Font.PLAIN, 16));

            UIManager.put("FileChooser.noPlacesBar", Boolean.TRUE);
            UIManager.put("FileChooser.readOnly", Boolean.TRUE);
        } catch (Exception e) {
            e.printStackTrace(System.err);
        }
    }

    private static void checkOSCompatability() {
        String osName = System.getProperty("os.name").toLowerCase();
        if (!osName.contains("win")) {
            if (debug) System.err.println("This program is not compatible with your operating system.");
            // JOptionPane.showMessageDialog(null, "This program is not compatible with your operating system.", "Error!", JOptionPane.ERROR_MESSAGE);
            JOptionPane.showMessageDialog(null, "非常抱歉，此程序与您的操作系统不兼容，目前只支持windows。", "Error!", JOptionPane.ERROR_MESSAGE);
            System.exit(1);
        }
    }

    private static void getJarPath() {
        try {
            // java读取当前jar文件中文件 https://blog.51cto.com/u_16213388/6984779
            // 获取当前JAR文件的路径
            jarPath = Paths.get(MainWorker.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getParent().toString();
            // Jar Path: D:\work\gitee\java-learn-demo\java-tools\target
            if (debug) System.out.println("Jar Path: " + jarPath);
        } catch (URISyntaxException e) {
            e.printStackTrace(System.err);
            System.err.println("[ERROR] Failed to get jar path.");
        }
    }

    private static void prefs() {
        format = prefs.get("format", "jpg");
        /**
         * System.getProperty()获取系统变量 https://blog.csdn.net/qq_40834180/article/details/123624204
         * user.home	用户的主目录
         * user.dir	用户的当前工作目录
         */
        selectedFilePath = prefs.get("selectedFilePath", System.getProperty("user.home"));
        deleteOriginal = prefs.getBoolean("deleteOriginal", false);

        /**
         * Runtime.getRuntime().addShutdownHook(Thread hook) 方法是Java提供的一个非常实用的功能。
         * 它可以注册一个JVM关闭的钩子，当JVM关闭时，会执行注册的线程。
         * 这个功能可以用于实现一些在JVM关闭时需要执行的重要操作，例如保存临时数据、清理资源、通知系统进行自我注销等。
         * 注意：
         * 如果JVM因为System.exit()或者其他强制退出而关闭，那么注册的钩子将不会被调用。
         * 如果钩子执行时间过长，或者执行时抛出异常，可能会导致JVM强制终止钩子的执行并退出。
         * 同一个钩子可以注册多次，但是只有在JVM关闭时才会执行一次，执行的顺序是按照注册的顺序。
         */
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            prefs.put("format", format);
            prefs.put("selectedFilePath", selectedFilePath);
            prefs.putBoolean("deleteOriginal", deleteOriginal);
        }));
    }

    private static void copyBinaryTempFile() {
        try (InputStream binaryPathStream = MainWorker.class.getClassLoader().getResourceAsStream("main/libs/heif-convert.exe")) {
            if (binaryPathStream == null) {
                System.err.println("Could not find binary file: heif-convert.exe");
                return;
            }
            Path outputPath = new File(heifConvertPath).toPath();

            Files.copy(binaryPathStream, outputPath, StandardCopyOption.REPLACE_EXISTING);
            Files.setAttribute(outputPath, "dos:hidden", true);

            Runtime.getRuntime().addShutdownHook(new Thread(() -> deleteFile(heifConvertPath)));

        } catch (Exception e) {
            e.printStackTrace(System.err);
        }
    }

    private static void deleteFile(String path) {
        File fileToDelete = new File(path);
        String name = fileToDelete.getName();
        if (fileToDelete.exists()) {
            if (fileToDelete.delete()) {
                if (debug) System.out.println("Deleted file: " + name);
            } else {
                System.err.println("Failed to delete file: " + name);
            }
        }
    }

    private static int totalFiles;
    private static int currentFile;

    protected static void convertImage() {
        convertButton.setEnabled(false);

        // show warning confirmation dialog
        if (deleteOriginal) {
            // int dialogResult = JOptionPane.showConfirmDialog(frame, "Deleting original files is enabled. \nAre you sure you want to continue?", "Warning!", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
            int dialogResult = JOptionPane.showConfirmDialog(frame, "已启用删除原始文件。 \n你确定要继续吗？", "Warning!", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
            if (dialogResult == JOptionPane.NO_OPTION) {
                if (debug) System.out.println("User cancelled conversion.");
                return;
            }
        }

        // check if file or directory is selected
        if (selectedFilePath == null || selectedFilePath.isEmpty()) {
            selectedFilePath = "";
            MainWindow.convertButton.setEnabled(false);
            // JOptionPane.showMessageDialog(frame, "Please select a file or directory.", "Error!", JOptionPane.ERROR_MESSAGE);
            JOptionPane.showMessageDialog(frame, "请选择一个文件或目录。", "Error!", JOptionPane.ERROR_MESSAGE);
            convertButton.setEnabled(true);
            return;
        }

        // single file conversion
        if (selectedFilePath.toLowerCase().endsWith(".heic")) {
            EventQueue.invokeLater(() -> {
                progressBar.setVisible(true);
                progressBar.setMaximum(100);
                progressBar.setValue(99);
                progressBar.setIndeterminate(false);
                progressBar.setString("Converting 1 of 1 file...");
            });

            convertProcess(selectedFilePath, 1, 1);

            if (deleteOriginal) {
                deleteFile(selectedFilePath);
            }

            convertButton.setEnabled(true);
            return;
        }

        // directory conversion
        Set<String> heicFiles = getHeicFiles(selectedFilePath);
        if (heicFiles == null) {
            JOptionPane.showMessageDialog(frame, "No HEIC files found in directory.", "Error!", JOptionPane.ERROR_MESSAGE);
            if (debug) System.err.println("No HEIC files found in directory: " + selectedFilePath);
            convertButton.setEnabled(true);
            return;
        }

        new Thread(() -> {
            totalFiles = heicFiles.size();
            currentFile = 0;

            progressBar.setVisible(true);
            progressBar.setMaximum(totalFiles);
            progressBar.setValue(0);
            progressBar.setIndeterminate(false);

            for (String heicFile : heicFiles) {
                String filePath = selectedFilePath + fileDiv + heicFile;
                convertProcess(filePath, ++currentFile, totalFiles);

                progressBar.setValue(currentFile);
                progressBar.setString("Converting file: " + currentFile + " of " + totalFiles + " files...");

                if (deleteOriginal) {
                    deleteFile(filePath);
                }
            }
            convertButton.setEnabled(true);
        }).start();
    }

    private static Set<String> getHeicFiles(String inputDirectory) {
        // get list of all heic files in input directory
        Set<String> files = Stream.of(Objects.requireNonNull(new File(inputDirectory).listFiles()))
                .filter(file -> !file.isDirectory())
                .map(File::getName)
                .collect(Collectors.toSet());

        if (debug) System.out.println("All Files: \n" + files);

        // filter out non-heic files
        Set<String> heicFiles = new HashSet<>();
        for (String file : files) {
            if (file.toLowerCase().endsWith(".heic")) {
                heicFiles.add(file);
            }
        }

        if (heicFiles.isEmpty()) {
            return null;
        }

        if (debug) System.out.println("HEIC Files: \n" + heicFiles);
        return heicFiles;
    }

    // private static void convertProcess(String filePath, int currentIndex, int totalIndex) {
    //     String[] cmd = {heifConvertPath, filePath, "-f", format};
    //     if (debug) System.out.println("Running command: " + String.join(" ", cmd));
    //
    //     String outputPath = filePath.substring(0, filePath.lastIndexOf(".")) + "." + format;
    //     File outputFile = new File(outputPath);
    //     if (outputFile.exists()) {
    //         if (debug) System.out.println("File already exists, skipping: " + outputPath);
    //
    //         totalFiles--;
    //         currentFile--;
    //
    //         if (currentIndex == totalIndex) {
    //             if (currentFile <= 0 && totalFiles <= 0) {
    //                 EventQueue.invokeLater(() -> progressBar.setVisible(false));
    //
    //                 JOptionPane.showMessageDialog(frame, "File(s) already exist! \nNo file(s) were converted.", "Error!", JOptionPane.ERROR_MESSAGE);
    //             } else {
    //                 showFinishedDialog(totalIndex);
    //             }
    //         }
    //         return;
    //     }
    //
    //     if (debug) System.out.println("Converting file: " + filePath);
    //
    //     try {
    //         ProcessBuilder pb = new ProcessBuilder(cmd);
    //         pb.redirectErrorStream(true);
    //         Process p = pb.start();
    //         Scanner s = new Scanner(p.getInputStream());
    //         while (s.hasNextLine()) {
    //             if (s.nextLine().contains("Wrote")) {
    //                 if (debug) System.out.println("Wrote file: " + outputPath);
    //                 if (currentIndex == totalIndex) {
    //                     showFinishedDialog(totalIndex);
    //                 } else {
    //                     if (debug) System.out.println("Finished converting file " + currentIndex + " of " + totalIndex);
    //                 }
    //             } else {
    //                 System.err.println("Something went wrong: \n" + s.nextLine());
    //             }
    //         }
    //         p.waitFor();
    //         if (debug) System.out.println("Exit value: " + p.exitValue());
    //     } catch (Exception e) {
    //         e.printStackTrace(System.err);
    //     }
    // }

    // 调用imagemagick
    private static void convertProcess(String filePath, int currentIndex, int totalIndex) {
        if (debug) System.out.println("filePath: " + heifConvertPath);

        String outputPath = filePath.substring(0, filePath.lastIndexOf(".")) + "." + format;
        File outputFile = new File(outputPath);
        if (outputFile.exists()) {
            if (debug) System.out.println("File already exists, skipping: " + outputPath);

            totalFiles--;
            currentFile--;

            if (currentIndex == totalIndex) {
                if (currentFile <= 0 && totalFiles <= 0) {
                    EventQueue.invokeLater(() -> progressBar.setVisible(false));

                    JOptionPane.showMessageDialog(frame, "File(s) already exist! \nNo file(s) were converted.", "Error!", JOptionPane.ERROR_MESSAGE);
                } else {
                    showFinishedDialog(totalIndex);
                }
            }
            return;
        }

        if (debug) System.out.println("Converting file: " + filePath);

        try {
            HeicToJpegUtil.heicToJpeg(new File(filePath), outputFile);
            if (currentIndex == totalIndex) {
                showFinishedDialog(totalIndex);
            } else {
                if (debug) System.out.println("Finished converting file " + currentIndex + " of " + totalIndex);
            }

            /*ProcessBuilder pb = new ProcessBuilder(cmd);
            pb.redirectErrorStream(true);
            Process p = pb.start();
            Scanner s = new Scanner(p.getInputStream());
            while (s.hasNextLine()) {
                if (s.nextLine().contains("Wrote")) {
                    if (debug) System.out.println("Wrote file: " + outputPath);
                    if (currentIndex == totalIndex) {
                        showFinishedDialog(totalIndex);
                    } else {
                        if (debug) System.out.println("Finished converting file " + currentIndex + " of " + totalIndex);
                    }
                } else {
                    System.err.println("Something went wrong: \n" + s.nextLine());
                }
            }
            p.waitFor();
            if (debug) System.out.println("Exit value: " + p.exitValue());*/
        } catch (Exception e) {
            e.printStackTrace(System.err);
        }
    }

    private static void showFinishedDialog(int totalIndex) {
        EventQueue.invokeLater(() -> { // dialog is modal but is called from a separate thread
            progressBar.setVisible(false);
            JOptionPane.showMessageDialog(frame, "Finished converting " + totalIndex + " file(s).", "Finished!", JOptionPane.INFORMATION_MESSAGE);
        });
    }

    protected static Icon getApplicationIcon(String internalPath, int width, int height) {
        Icon icon = null;
        try (InputStream iconStream = MainWorker.class.getClassLoader().getResourceAsStream(internalPath)) {
            if (iconStream != null) {
                // set scale as well as icon
                icon = new ImageIcon(ImageIO.read(iconStream).getScaledInstance(width, height, Image.SCALE_SMOOTH));

            }
        } catch (Exception e) {
            if (debug) e.printStackTrace(System.err);
        }
        if (icon == null) {
            System.err.println("[ERROR] Could not find icon file at: " + internalPath);
        }
        return icon;
    }
}
